package com.lucky_byte.pay.jar.yins;

import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.json.JSONObject;
import org.json.XML;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.lucky_byte.pay.jar.Packet;

/**
 * 银盛支付报文 v2.0
 */
public class PacketY20 extends Packet
{
	private static final Logger logger = LogManager.getLogger();

	private static AtomicInteger msg_id_counter = new AtomicInteger();

	private String head_src;
	private String head_msg_code;
	private String head_msg_id;
	private String head_check;
	private MsgXML msg_xml;
	private String orig_msg_data;

	public PacketY20() {
		this.head_src = "";
		this.head_msg_code = "";
		String date = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
		this.head_msg_id = date + "-" + msg_id_counter.getAndIncrement();
		this.head_check = "";
		msg_xml = new MsgXML();
	}

	public void setHeadSrc(String src) {
		if (src != null) {
			this.head_src = src;
		}
	}

	public String getHeadSrc() {
		return this.head_src;
	}

	public void setHeadMsgCode(String msg_code) {
		if (msg_code != null) {
			this.head_msg_code = msg_code;
		}
	}

	public String getHeadMsgCode() {
		return this.head_msg_code;
	}

	public void setHeadMsgId(String msg_id) {
		if (msg_id != null) {
			this.head_msg_id = msg_id;
		}
	}

	public String getHeadMsgId() {
		return this.head_msg_id;
	}

	public void setHeadCheck(String check) {
		if (check != null) {
			this.head_check = check;
		}
	}

	public String getHeadCheck() {
		return this.head_check;
	}

	public MsgXML getMsgXML() {
		return msg_xml;
	}

	public String getMsgXMLString() {
		return msg_xml.toXMLString();
	}

	public byte[] getMsgXMLBytes() {
		try {
			return this.getMsgXMLString().getBytes("GBK");
		} catch (UnsupportedEncodingException e) {
			logger.error("系统不支持[GBK]编码.");
			return null;
		}
	}

	public boolean setMsgXMLString(String xml_string) {
		return this.msg_xml.parse(xml_string);
	}

	public String getOrigMsgData() {
		return this.orig_msg_data;
	}

	@Override
	public boolean has(String name) {
		String value = msg_xml.getNodeValue(name);
		if (value == null) {
			return false;
		}
		return true;
	}

	@Override
	public String get(String name) {
		return msg_xml.getNodeValue(name);
	}

	@Override
	public boolean set(String name, String value) {
		if (!this.has(name)) {
			return false;
		}
		msg_xml.setNodeValue(name, value);
		return true;
	}

	@Override
	public int count() {
		logger.warn("PacketY20 不支持此函数，请通过 MsgXML 接口访问 ...");
		return 0;
	}

	@Override
	public void remove(String name) {
		logger.warn("PacketY20 不支持此函数，请通过 MsgXML 接口访问 ...");
	}

	@Override
	public void clear() {
		logger.warn("PacketY20 不支持此函数，请通过 MsgXML 接口访问 ...");
	}

	@Override
	public List<String> keySet() {
		logger.warn("PacketY20 不支持此函数，请通过 MsgXML 接口访问 ...");
		return null;
	}

	@Override
	public Packet clone() {
		PacketY20 clone = new PacketY20();

		clone.head_src = new String(this.head_src);
		clone.head_msg_code = new String(this.head_msg_code);
		clone.head_msg_id = new String(this.head_msg_id);
		clone.head_check = new String(this.head_check);
		clone.msg_xml = this.msg_xml.clone();
		return clone;
	}

	@Override
	public JsonObject toJson() {
		JsonObject json_object = new JsonObject();

		json_object.addProperty("src", this.head_src);
		json_object.addProperty("msg_code", this.head_msg_code);
		json_object.addProperty("msg_id", this.head_msg_id);
		json_object.addProperty("check", this.head_check);
		json_object.add("msg", this.msg_xml.toJson());
		return json_object;
	}

	/**
	 * 打包为网络传输数据(定长格式)，注意请求报文不能使用这种格式.
	 */
	public String pack() {
		StringBuilder builder = new StringBuilder();
		builder.append(String.format("%1$-20s", this.head_src));
		builder.append(String.format("%1$-5s", this.head_msg_code));
		builder.append(String.format("%1$-32s", this.head_msg_id));
		builder.append(String.format("%1$-256s", this.head_check));
		byte[] msg_bytes = this.getMsgXMLBytes();
		if (msg_bytes == null) {
			logger.error("获取 msg 内容失败，pack() 失败.");
			return null;
		}
		builder.append(Base64.toBase64String(msg_bytes));
		return builder.toString();
	}

	/**
	 * 打包为网络传输数据(表单格式)，请求报文采用此格式
	 */
	public Map<String, String> packMap() {
		Map<String, String> ret_map = new HashMap<>();
		ret_map.put("src", this.head_src);
		ret_map.put("msgCode", this.head_msg_code);
		ret_map.put("msgId", this.head_msg_id);
		ret_map.put("check", this.head_check);
		byte[] msg_bytes = this.getMsgXMLBytes();
		if (msg_bytes == null) {
			logger.error("获取 msg 内容失败，packMap() 失败.");
			return null;
		}
		String msg = Base64.toBase64String(msg_bytes);
		ret_map.put("msg", msg);

		logger.trace("打包[src][{}].", this.head_src);
		logger.trace("打包[msgCode][{}].", this.head_msg_code);
		logger.trace("打包[msgId][{}].", this.head_msg_id);
		logger.trace("打包[check][{}].", this.head_check);
		logger.trace("打包[msg][{}].", msg);

		return ret_map;
	}

	/**
	 * 解析从网络接收的原始数据
	 */
	public boolean parse(String data) {
		if (data == null) {
			logger.error("解析 PacketY20 数据错误，数据为空.");
			return false;
		}
		if (data.length() < 313) {
			logger.error("解析 PacketY20 数据错误，数据长度[{}]不足.", data.length());
			return false;
		}
		logger.trace("开始读取 PacketY20 报文...");

		this.head_src = data.substring(0, 20).trim();
		this.head_msg_code = data.substring(20, 25).trim();
		this.head_msg_id = data.substring(25, 57).trim();
		this.head_check = data.substring(57, 313).trim();
		this.orig_msg_data = data.substring(313).trim();

		logger.trace("读取[src][{}]", this.head_src);
		logger.trace("读取[msgCode][{}]", this.head_msg_code);
		logger.trace("读取[msgId][{}]", this.head_msg_id);
		logger.trace("读取[check][{}]", this.head_check);
		logger.trace("读取[msg][{}]", this.orig_msg_data);

		// 继续解析 XML 数据
		try {
			byte[] bytes = Base64.decode(this.orig_msg_data);
			String string = new String(bytes, "GBK");
			logger.debug("解码[msg][{}].", string);
			if (!this.msg_xml.parse(string)) {
				logger.error("解析[msg]内容[{}]错误.", string);
				return false;
			}
			return true;
		} catch (UnsupportedEncodingException e) {
			logger.error("系统不支持编码[GBK].");
			return false;
		}
	}

	/**
	 * 处理 Msg 节点，Msg 节点是一个 XML
	 */
	public class MsgXML {
		Document xmldoc;
		DocumentBuilderFactory xml_factory;
		XPathFactory xpath_factory;

		public MsgXML() {
			try {
				xml_factory = DocumentBuilderFactory.newInstance();
				xpath_factory = XPathFactory.newInstance();
				DocumentBuilder builder = xml_factory.newDocumentBuilder();
				xmldoc = builder.newDocument();
				Element root = xmldoc.createElement("yspay");
				xmldoc.appendChild(root);
				root.appendChild(xmldoc.createElement("head"));
				root.appendChild(xmldoc.createElement("body"));
			} catch (Exception e) {
				logger.error("新建 XML 文档失败[{}][{}].", e.getMessage(),
						e.getClass().getSimpleName());
			}
		}

		/**
		 * 通过 XPath 获取字符串值
		 */
		public String getNodeValue(String expression) {
			try {
				XPath xpath = xpath_factory.newXPath();
				XPathExpression expr = xpath.compile(expression);
				Object value = expr.evaluate(xmldoc);
				if (value == null || !(value instanceof String)) {
					return null;
				}
				return (String) value;
			} catch (XPathExpressionException e) {
				logger.error("编译 XPath 表达式[{}]错误[{}][{}].", expression,
						e.getMessage(), e.getClass().getSimpleName());
				return null;
			}
		}

		/**
		 * 通过 XPath 设置节点的值
		 * 这个函数不能添加新节点
		 */
		public void setNodeValue(String expression, String value) {
			try {
				XPath xpath = xpath_factory.newXPath();
				XPathExpression expr = xpath.compile(expression);
				Node node = (Node) expr.evaluate(xmldoc, XPathConstants.NODE);
				node.setNodeValue(value);
			} catch (Exception e) {
				logger.error("编译 XPath 表达式[{}]错误[{}][{}].", expression,
						e.getMessage(), e.getClass().getSimpleName());
			}
		}

		/**
		 * 解析 XML 字符串
		 */
		public boolean parse(String xml_string) {
			try {
				DocumentBuilder builder = xml_factory.newDocumentBuilder();
				xmldoc = builder.parse(
						new InputSource(new StringReader(xml_string)));
			} catch (Exception e) {
				logger.error("解析XML[{}]错误[{}][{}].", xml_string,
						e.getMessage(), e.getClass().getSimpleName());
				return false;
			}
			return true;
		}

		/**
		 * 转换为XML字符串
		 */
		public String toXMLString() {
			try {
				TransformerFactory factory = TransformerFactory.newInstance();
				Transformer transformer = factory.newTransformer();
				transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
				transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
				transformer.setOutputProperty(OutputKeys.ENCODING, "GBK");
				StringWriter writer = new StringWriter();
				transformer.transform(new DOMSource(xmldoc), new StreamResult(writer));
				return writer.getBuffer().toString();
			} catch (Exception e) {
				logger.error("转换XML到字符串失败[{}][{}].", e.getMessage(),
						e.getClass().getSimpleName());
				return null;
			}
		}

		/**
		 * 克隆整个文档
		 */
		public MsgXML clone() {
			MsgXML clone = new MsgXML();
			clone.xmldoc = (Document) this.xmldoc.cloneNode(true);
			return clone;
		}

		/**
		 * 转换为 JSON
		 */
		public JsonObject toJson() {
			JSONObject object = XML.toJSONObject(this.toXMLString());
			String json_str = object.toString();
			return new JsonParser().parse(json_str).getAsJsonObject();
		}
	}

}
